package model;

import java.awt.Color;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.UUID;

import javax.swing.ImageIcon;

import model.AI.MapInfo;

/**
 * This class represents a player in the game. It contains the player's name,
 * ImageIcon, their number of cards, number armies, number of countries, color
 * and a map reference. The Player is updated by the client when it recieves
 * information from the server.
 * 
 * @author David Shefchik, Xiaotian Chang
 * 
 */
public abstract class Player extends Observable implements Serializable {

	/**
	 * An enum representing what part of the player's turn is currently being
	 * carried out.
	 * 
	 * @author shefchik
	 * 
	 */
	public enum PlayerTurn {
		FALSE, CARDS, BONUS, ATTACK, FORTIFY, START
	}

	private static final long serialVersionUID = -1947665607500854075L;

	private Map map;
	private List<Card> cards;
	private int bonusArmiesReceived;
	private int diceAttack;
	private int diceDefend;
	private boolean cardEligible;
	private UUID uuid;
	private boolean turnIsMine;
	private PlayerTurn turn;

	protected int troopsToAdd;
	protected Profile profile;

	// TODO Make sure every time a new Map's sent/created, the Player is updated
	// with such too

	/**
	 * This constructor serves as the super constructor to HumanPlayer,
	 * SimpleCyborg, and ExpertCyborg. The code looks convoluted, but it is
	 * simply accomplishing one of two tasks, depending on the situation that
	 * arises. First, it tries to read a 'profile.dat' file stored on the hard
	 * disk, but if it is not there, it will create and serialize a new Profile
	 * object to the disk to be retrieved at later game plays.
	 * 
	 * @param name
	 *            is the name of this player.
	 * @param color
	 *            is the color set to this player.
	 * @param iIcon
	 *            is the ImageIcon of this player.
	 * @param map
	 *            is the map in which this player exist.
	 */
	public Player(String name, Color color, ImageIcon iIcon) {
		this.profile = new Profile(name, color, iIcon);
		

		this.troopsToAdd = 0;
		this.cards = new ArrayList<Card>();
		diceAttack = 2;
		diceDefend = 1;
		setUUID(UUID.randomUUID());
		turn = PlayerTurn.FALSE;
	}

	/**
	 * Returns the name of the player.
	 * 
	 * @return Gets the name of the player.
	 */
	public String getName() {
		return profile.getName();
	}

	/**
	 * Takes in a String and sets the name of the player to that string.
	 * 
	 * @param str
	 *            Sets the name of the Player to the one passed.
	 */
	public void setName(String str) {
		profile.setName(str);
	}

	/**
	 * Gets the player's instance of Map
	 * 
	 * @return The Map instance
	 */
	public Map getMap() {
		return map;
	}

	/**
	 * Sets the players Map
	 * 
	 * @param map
	 */
	public void setMap(Map map) {
		this.map = map;
	}

	/**
	 * Returns the number of armies a player currently has.
	 * 
	 * @return the total number of armies a player has.
	 */
	public int getNumArmies() {
		ArrayList<Territory> Territories = new ArrayList<Territory>();
		int numArmies = 0;
		for (Continent tempContinent : map.listContinents()) {
			for (Territory tempTerritory : tempContinent.listTerritory()) {
				if (tempTerritory.getRuler().getName() == profile.getName()) {
					Territories.add(tempTerritory);
				}
			}
		}
		for (Territory temp : Territories) {
			numArmies += temp.getTroopNumber();
		}
		return numArmies + this.bonusArmiesReceived;
	}

	/**
	 * Returns an integer representing the total number of armies a player has
	 * in the field
	 * 
	 * @return An integer representing field armies
	 */
	public int getNumFieldArmies() {

		return getNumArmies() - this.getBonus();
	}

	/**
	 * Returns the number of territories a player currently owns
	 * 
	 * @return the numCountries
	 */
	public int getNumTerritories() {
		return MapInfo.listPlayerTerritory(this).size();
	}

	/**
	 * Sets the color of the player for map and profile representation
	 * 
	 * @param color
	 *            the color to set
	 */
	public void setColor(Color color) {
		profile.setColor(color);
	}

	/**
	 * Returns the color of the player
	 * 
	 * @return the color
	 */
	public Color getColor() {
		return profile.getColor();
	}

	/**
	 * Returns the number of cards the player currently has
	 * 
	 * @return the numCards
	 */
	public int getNumCards() {
		return this.cards.size();
	}

	/**
	 *Returns the total number of bonus troops the player has
	 * 
	 * @return the bonus troop of this player.
	 */
	public int getBonus() {
		return this.bonusArmiesReceived;
	}

	/**
	 * Adds the given integer representing added bonus troops to the Player's
	 * total
	 * 
	 * @param toAdd
	 *            integer value to add to bonus
	 * @return true if toAdd is positive.
	 */
	public boolean addBonus(int toAdd) {
		if (toAdd > 0) {
			this.bonusArmiesReceived = toAdd;
			this.troopsToAdd = toAdd;
			return true;
		} else {
			return false;
		}
	}

	/**
	 * @param toRemove
	 * @return true if remove is successful made.
	 */
	public boolean removeBonus(int toRemove) {
		if (toRemove > 0 && this.bonusArmiesReceived >= toRemove) {
			this.bonusArmiesReceived -= toRemove;
			this.troopsToAdd -= toRemove;
			return true;
		} else
			return false;
	}

	/**
	 * Sets the ImageIcon of the player
	 * 
	 * @param image
	 *            the image to set
	 */
	public void setImage(ImageIcon image) {
		profile.setImage(image);
	}

	/**
	 * Returns the ImageIcon of the player
	 * 
	 * @return the image
	 */
	public ImageIcon getImage() {
		return profile.getImage();
	}

	/**
	 * Returns a boolean value representing whether the player is eligible to
	 * receive a card at the end of their attack phase. They only receive a card
	 * if they conquered an enemy territory during their attack phase.
	 * 
	 * @return A value representing whether the Player is eligible to receive a
	 *         card.
	 */
	public boolean isCardEligible() {
		if (cardEligible)
			return true;

		return false;
	}

	/**
	 * This method toggles the boolean representing whether the player is
	 * eligible to recieve a card at the end of his or her turn or not.
	 * 
	 * @param value
	 *            The boolean value stating whether the player is eligible
	 */
	public void setCardEligible(boolean value) {
		cardEligible = value;

	}

	/**
	 * Adds a card enum to the player's list of cards.
	 * 
	 * @param card
	 *            The Card being added to the list.
	 */
	public void receiveCard(Card card) {
		if (card.getType() != CardType.WILD) {
			System.out.println("receive card:	"
					+ card.getTerritory().getTerritoryName());
			System.out.println("receive card:	" + card.getType().toString());
		}
		this.cards.add(card);
	}

	/**
	 * Removes a given card from the Player's list
	 * 
	 * @param card
	 *            is the card to remove
	 */
	public boolean removeCard(Card card) {
		return this.cards.remove(card);
	}

	/**
	 * Returns a reference to the player's list of cards.
	 * 
	 * @return a list which contains all cards this player has.
	 */
	public List<Card> getCards() {
		return this.cards;
	}

	/**
	 * @param source
	 *            is a observable class
	 * @param data
	 *            is the data changed.
	 * 
	 */
	public void update(Observable source, Object data) {
		System.out.println("Player: update()");
		if (source instanceof Map) {
			Map tempMap = (Map) source;
			this.map = tempMap;
		}
	}

	/**
	 *Sets the player's default dice setting for attacking
	 * 
	 * @param diceAttack
	 *            the diceAttack to set
	 */
	public void setDiceAttack(int diceAttack) {
		this.diceAttack = diceAttack;
	}

	/**
	 * Returns the player's default dice setting fo attacking
	 * 
	 * @return the diceAttack
	 */
	public int getDiceAttack() {
		return diceAttack;
	}

	/**
	 * @param diceDefend
	 *            the diceDefend to set
	 */
	public void setDiceDefend(int diceDefend) {
		this.diceDefend = diceDefend;
	}

	/**
	 * @return the diceDefend
	 */
	public int getDiceDefend() {
		return diceDefend;
	}

	private void setUUID(UUID uuid) {
		this.uuid = uuid;
	}

	public UUID getUUID() {
		return uuid;
	}

	public PlayerTurn getTurnPhase() {
		return turn;
	}

	public void setTurnPhase(PlayerTurn t) {
		turn = t;
	}

}
